Tools to handle trait macroecological datasets in R

The rmacroRDM package contains functions to help with the compilation of macroecological datasets. It compiles datasets into a master long database of individual observations, matched to a specified master species list. It also checks, separates and stores taxonomic and metadata information on the observations, variables and datasets contained in the data. It therefore aims to ensure full traceability of datapoints and as robust quality control, all the way through to the extracted analytical datasets.

For more details and context, see the rmacroRDM github repo

R code for this workflow available here



1 setup

1.1 source rmacroRDM functions

First source the rmacroRDM functions. Currently the best way is to just source from github using RCulr:getURL().

WARNING: Repo under continuous development

require(RCurl)
eval(parse(text = getURL("https://raw.githubusercontent.com/annakrystalli/rmacroRDM/master/R/functions.R", ssl.verifypeer = FALSE)))
eval(parse(text = getURL("https://raw.githubusercontent.com/annakrystalli/rmacroRDM/master/R/wideData_function.R", ssl.verifypeer = FALSE)))

1.2 setup file.system

Next, to initialise the project we need to supply valid pathways to project folders containing:

  • data: folder containg private input and output folders are stored, usually a googledrive folder) and
  • code: folder containing scripts associated with the project. This is usually an RStudio project directory, ideally version controlled on github.

The function sets those directories in the global environment.

setDirectories(script.folder = "~/Documents/workflows/Brain_size_evolution/", 
               data.folder = "~/Google Drive/Brain evolution/",
               envir = globalenv())

Once project folders have been set, we set up the file system by creating the required folders (if they don’t exist already) in the project folders.

setupFileSystem(script.folder = "~/Documents/workflows/Brain_size_evolution/", 
                data.folder = "~/Google Drive/Brain evolution/")

1.3 initialise database configurations and attach to search pathway

In this step, we initialise the environment with some required parameters to build the database and process files to it. The call below shows the default initialisation settings which you would get if you just called inti_db()

init_db(var.vars = c("var", "value", "data.ID"),
                    match.vars = c("synonyms", "data.status"),
                    meta.vars = c("qc", "observer", "ref", "n", "notes"),
                    taxo.vars = c("genus", "family", "order"),
                    spp.list_src = NULL)

I actually want to set “D0” as the file from which to extract the spp.list in a bit so I set spp.list_src = "D0".

init_db(spp.list_src = "D0")
configuring: 
master.vars
match.vars
meta.vars
spp.list_src
taxo.vars
var.vars
NULL

The function appends the given arguments to environment master_config at position 2 in the search path (note position of GlobalEnvironment = 1).

Here’s a list of the values of the objects we just attached as configurations:

1.4 setup input.folder

Next we setup the folders in input.folder/pre/ and post/ according to the configurations set in the previous step.

If the correct setup already exists, no action is taken:

setupInputFolder(input.folder)
dirs <- list.dirs(paste(input.folder, "pre/", sep = ""), full.names = T)

print(dirs)
[1] "/Users/Anna/Google Drive/Brain evolution/inputs/data/pre/"         
[2] "/Users/Anna/Google Drive/Brain evolution/inputs/data/pre//csv"     
[3] "/Users/Anna/Google Drive/Brain evolution/inputs/data/pre//n"       
[4] "/Users/Anna/Google Drive/Brain evolution/inputs/data/pre//notes"   
[5] "/Users/Anna/Google Drive/Brain evolution/inputs/data/pre//observer"
[6] "/Users/Anna/Google Drive/Brain evolution/inputs/data/pre//qc"      
[7] "/Users/Anna/Google Drive/Brain evolution/inputs/data/pre//ref"     



2 populate file.system

The functions take advantage of the structure of the file.sytem to automate loading and linking of data and metadata through appropriate naming and location of files within the file.system.

  • raw/ organise all raw data
  • pre/ save copies of the raw data files in the appropriate data (cvs) or meta.vars folders.

NB meta.var data sheets should be named with the same name as the data data sheet. Take care during this stage to ensure files are named correctly and stored in the appropriate folders.

3 load required data

3.1 make fcodes (folder codes) vector

The fcodes vector specifies the details of folders in pre/ and post/ input.folder folders. It also creates appropriate code prefixes for each type of data or meta.var sheet. Note that “D” is reserved for data files, “R” for ref files and “N” for n files.

fcodes <- ensure_fcodes(meta.vars)
print(fcodes)
         D          R          N          Q          O         NO 
     "csv"      "ref"        "n"       "qc" "observer"    "notes" 

3.2 set file.names vector

Specify the file.names of the files you wish to process. If you want to process all files in the file.system use file.names = NULL.

file.names <- create_file.names(file.names = c("brainmain2.csv", 
                                  "Amniote_Database_Aug_2015.csv", "anagedatasetf.csv"))
dcodes succesfully extracted from 'data_log.csv'
print(file.names)
                             x0                              x1 
               "brainmain2.csv" "Amniote_Database_Aug_2015.csv" 
                             x2 
            "anagedatasetf.csv" 

3.3 load system reference files

Load the system reference (sys.ref) files required for data processing.

load_sys.ref(fileEncoding = "mac", view = F)
loading: 
metadata
data_log
vnames
attaching to env: sys.ref
NULL

*use view = T to open a viewer for each of the sys.ref files on load.

3.3.1 metadata.csv

kable(head(metadata, 10))
code cat descr scores levels type units
species NOMINAL Scientific name NA NA NA NA
brain.volume MORPHOLOGICAL Brain volume NA NA CON mm
brain.mass MORPHOLOGICAL Brain mass NA NA CON g
male.brain.mass MORPHOLOGICAL Male brain mass NA NA CON g
female.brain.mass MORPHOLOGICAL Female brain mass NA NA CON g
body.mass MORPHOLOGICAL Body mass NA NA CON g
male.body.mass MORPHOLOGICAL Male body mass NA NA CON g
female.body.mass MORPHOLOGICAL Female body mass NA NA CON g
telencephalic.volume.fraction MORPHOLOGICAL Telencephalic volume fraction NA NA CON mm
female.maturity LIFE HISTORY TRAIT Female maturity NA NA INT d

3.3.2 data_log.csv

kable(data_log)
dcode file.name descr source source.contact method notes
D0 brainmain2.csv NA NA NA NA NA
D1 Amniote_Database_Aug_2015.csv NA NA NA NA NA
D2 anagedatasetf.csv NA NA NA NA NA
D3 lifehistraits.csv NA NA NA NA NA
D4 parentalcare.csv NA NA NA NA NA

3.3.3 vnames.csv

kable(head(vnames, 10))
code D0 D1 D2 D3 D4 R1 N1
species Scientific name species Scientific.name Scientific name Scientific_name species species
genus NA genus NA NA NA genus genus
brain.volume_n n NA Sample.size NA NA NA NA
brain.volume Brain Vol. NA NA NA NA NA NA
brain.volume_ref Source brain vol. NA NA NA NA NA NA
brain.mass Brain mass (g) NA NA NA NA NA NA
brain.mass_n n brain mass NA NA NA NA NA NA
brain.mass_ref source brain mass NA NA NA NA NA NA
male.brain.mass male brain mass NA NA NA NA NA NA
male.brain.mass_n male brain mass no NA NA NA NA NA NA

3.4 load syn.links.csv

Used for taxonomic matching (plans to automate this by integrating package taxize. supplied syn.links only relates to birds).

syn.links <- read.csv(text=getURL("https://raw.githubusercontent.com/annakrystalli/rmacroRDM/master/data/input/taxo/syn.links.csv", 
                                  ssl.verifypeer = FALSE), header=T)



4 process file.system

This step processes the .csv copies of the raw data in the pre/ folder and writes the processed files as .csv to the post/ folder, ready to be matched. The processing conserves the file.names throughout. It is important for this step that the file.system is correctly populated (ie. meta.var data sheets should be named with the same name as the data data sheet and in the correct folder).


The function runs a basic processing stage for each file in file.names:

  • processing is iterated over:
    • all file.names available in the file.system if file.system = "fromFS",
    • files specified in file.names if file.names is vector of file names (note that only files available in the file.system are processed).
  • data are loaded, trimmed of whitespace, blank lines and c("", " ", "NA", "-999") coded as NAs by default.
  • column names is the data are matched to master variable codes through vnames
  • if the original dataset contains species details across two columns (ie species and genus), data in the columns are concatinated in the form "genus_species" and merged into a single species column. ensure column in files containing genus data is matched to code genus in vnames.


custom processing scripts

you can include extra processing scripts for individual files by adding them into the {script.folder}process/ folder. To be loaded correctly, scripts need to be named appropriately:

  • to source across all files in file.system matching the file.name: name script as file.name. eg ."Amniote_Database_Aug_2015.R"
  • to source for files in a specific folder in file.system matching the file.name: name script as file.name appended with appropriate fcode, eg ."Amniote_Database_Aug_2015_ref.R"
process_file.system(file.names, fcodes)
[1] "all files in file system have valid vnames columns"
===================================================================
processing Amniote_Database_Aug_2015.csv D1 
[1] "source complete"
sourced: process/Amniote_Database_Aug_2015.R
'genus_species' combined in species column. Genus data removed 
df nrow (species) : 9802 df ncol (traits) : 20 
*** 
===================================================================
processing anagedatasetf.csv D2 
df nrow (species) : 1191 df ncol (traits) : 18 
*** 
===================================================================
processing brainmain2.csv D0 
duplicate species name in D0
df nrow (species) : 2586 df ncol (traits) : 24 
*** 
===================================================================
processing Amniote_Database_Aug_2015.csv R1 
[1] "source complete"
sourced: process/Amniote_Database_Aug_2015.R
'genus_species' combined in species column. Genus data removed 
df nrow (species) : 9802 df ncol (traits) : 20 
*** 
===================================================================
processing Amniote_Database_Aug_2015.csv N1 
[1] "source complete"
sourced: process/Amniote_Database_Aug_2015.R
'genus_species' combined in species column. Genus data removed 
df nrow (species) : 9802 df ncol (traits) : 19 
*** 



5 Create database

5.1 create spp.list

Use spp.list_source to specify dcode of file.name to extract spp.list from. Otherwise, supply vector of species names to species.

spp.list <- createSpp.list(species = NULL, 
                           taxo.dat = NULL, 
                           spp.list_src = spp.list_src)
species list extracted from dataset: D0
 file.name: brainmain2.csv
str(spp.list, vec.len = 3)
'data.frame':   1906 obs. of  4 variables:
 $ species    : chr  "Aix_galericulata" "Aix_sponsa" "Alopochen_aegyptiaca" ...
 $ master.spp : logi  TRUE TRUE TRUE TRUE ...
 $ rel.spp    : logi  NA NA NA NA ...
 $ taxo.status: chr  "original" "original" "original" ...
 - attr(*, "type")= chr "spp.list"

5.2 create master shell

master <- create_master(spp.list)
str(master, max.level = 2, vec.len = 3)
List of 3
 $ data    :'data.frame':   0 obs. of  11 variables:
  ..$ species    : logi(0) 
  ..$ synonyms   : logi(0) 
  ..$ data.status: logi(0) 
  ..$ var        : logi(0) 
  ..$ value      : logi(0) 
  ..$ data.ID    : logi(0) 
  ..$ qc         : logi(0) 
  ..$ observer   : logi(0) 
  ..$ ref        : logi(0) 
  ..$ n          : logi(0) 
  ..$ notes      : logi(0) 
  ..- attr(*, "format")= chr "master"
 $ spp.list:'data.frame':   1906 obs. of  4 variables:
  ..$ species    : chr [1:1906] "Aix_galericulata" "Aix_sponsa" "Alopochen_aegyptiaca" ...
  ..$ master.spp : logi [1:1906] TRUE TRUE TRUE TRUE ...
  ..$ rel.spp    : logi [1:1906] NA NA NA NA ...
  ..$ taxo.status: chr [1:1906] "original" "original" "original" ...
  ..- attr(*, "type")= chr "spp.list"
 $ metadata:'data.frame':   38 obs. of  7 variables:
  ..$ code  : chr [1:38] "species" "brain.volume" "brain.mass" ...
  ..$ cat   : chr [1:38] "NOMINAL" "MORPHOLOGICAL" "MORPHOLOGICAL" ...
  ..$ descr : chr [1:38] "Scientific name" "Brain volume" "Brain mass" ...
  ..$ scores: chr [1:38] NA NA NA ...
  ..$ levels: chr [1:38] NA NA NA ...
  ..$ type  : chr [1:38] NA "CON" "CON" ...
  ..$ units : chr [1:38] NA "mm" "g" ...
 - attr(*, "type")= chr "master"
 - attr(*, "file.names")= chr "empty"



6 match new datasets to master

6.1 create match object from file.name.

The fuction loads the file specified by filename in input.folder/pre/csv/. The argument sub specifies which of the two sets of species to be matched (spp.list or data) is a subset (ie smaller) than the other. spp.list is the spp.list attached to the master.

filename <- file.names[file.names == "Amniote_Database_Aug_2015.csv"]

m <- matchObj(file.name = filename,
              spp.list = master$spp.list,
              sub = "spp.list") # use addMeta function to manually add metadata.
List of 7
 $ data.ID  : chr "D1"
 $ data     :'data.frame':  9802 obs. of  20 variables:
  ..- attr(*, "format")= chr "data:wide"
 $ spp.list :'data.frame':  1906 obs. of  4 variables:
  ..- attr(*, "type")= chr "spp.list"
 $ sub      : chr "spp.list"
 $ set      : chr "data"
 $ meta     :List of 5
  ..- attr(*, "type")= chr "meta"
 $ file.name: Named chr "Amniote_Database_Aug_2015.csv"
  ..- attr(*, "names")= chr "x1"
 - attr(*, "type")= chr "match.object"
 - attr(*, "status")= chr "unmatched"

6.2 compile metadata and prepare m for matching

m <- m %>% 
  separateDatMeta() %>% 
  compileMeta(input.folder = input.folder) %>%
  checkVarMeta(master$metadata) %>%
  dataMatchPrep()
[1] "============================================================================"
[1] "processing meta.var: qc"
[1] "Warning: NULL data for meta.var: qc"
[1] "============================================================================"
[1] "processing meta.var: observer"
[1] "Warning: NULL data for meta.var: observer"
[1] "============================================================================"
[1] "processing meta.var: ref"
[1] "loading 'ref/Amniote_Database_Aug_2015.csv'"
[1] "============================================================================"
[1] "processing meta.var: n"
[1] "loading 'n/Amniote_Database_Aug_2015.csv'"
[1] "n vars matched successfully to post/n/Amniote_Database_Aug_2015_n_group.csv"
[1] "============================================================================"
[1] "processing meta.var: notes"
[1] "Warning: NULL data for meta.var: notes"
[1] "D1 metadata complete"
List of 7
 $ data.ID  : chr "D1"
 $ data     :'data.frame':  9802 obs. of  22 variables:
 $ spp.list :'data.frame':  1906 obs. of  4 variables:
  ..- attr(*, "type")= chr "spp.list"
 $ sub      : chr "spp.list"
 $ set      : chr "data"
 $ meta     :List of 5
  ..- attr(*, "type")= chr "meta"
 $ file.name: Named chr "Amniote_Database_Aug_2015.csv"
  ..- attr(*, "names")= chr "x1"
 - attr(*, "type")= chr "match.object"
 - attr(*, "status")= chr "unmatched"

6.3 match m to spp.list

m <- dataSppMatch(m, syn.links = syn.links, addSpp = T)
Warning in dataSppMatch(m, syn.links = syn.links, addSpp = T): match
incomplete, 8 spp.list datapoints unmatched
List of 8
 $ data.ID  : chr "D1"
 $ data     :'data.frame':  1898 obs. of  22 variables:
  ..- attr(*, "format")= chr "data:wide"
 $ spp.list :'data.frame':  1906 obs. of  4 variables:
  ..- attr(*, "type")= chr "spp.list"
 $ sub      : chr "spp.list"
 $ set      : chr "data"
 $ meta     :List of 5
  ..- attr(*, "type")= chr "meta"
 $ file.name: Named chr "Amniote_Database_Aug_2015.csv"
  ..- attr(*, "names")= chr "x1"
 $ unmatched:'data.frame':  8 obs. of  2 variables:
 - attr(*, "type")= chr "match.object"
 - attr(*, "status")= chr "matched:incomplete - 8 spp.list unmatched"

6.3.1

output <- masterDataFormat(m, meta.vars, match.vars, var.vars)
Warning in masterDataFormat(m, meta.vars, match.vars, var.vars): 1575 data points missing reference information!:
 (9.6% of 16469)
str(output, max.level = 1, vec.len = 3)
List of 3
 $ data     :'data.frame':  16469 obs. of  11 variables:
  ..- attr(*, "format")= chr "master"
 $ spp.list :'data.frame':  1906 obs. of  4 variables:
  ..- attr(*, "type")= chr "spp.list"
 $ file.name: Named chr "Amniote_Database_Aug_2015.csv"
  ..- attr(*, "names")= chr "D1"
 - attr(*, "type")= chr "data:master"
 - attr(*, "status")= chr "matched"

6.3.2 merge to master

master <- updateMaster(master, output = output)
str(master, max.level = 1, vec.len = 3)
List of 3
 $ data    :'data.frame':   16469 obs. of  11 variables:
  ..- attr(*, "format")= chr "master"
 $ spp.list:'data.frame':   1906 obs. of  4 variables:
  ..- attr(*, "type")= chr "spp.list"
 $ metadata:'data.frame':   38 obs. of  7 variables:
 - attr(*, "type")= chr "master"
 - attr(*, "file.names")= Named chr "Amniote_Database_Aug_2015.csv"
  ..- attr(*, "names")= chr "D1"


7 interactive view of framework objects:

7.1 master

jsonedit(master)


7.2 matched m

jsonedit(m)
LS0tCnRpdGxlOiAicm1hY3JvUkRNX3dvcmtmbG93IgphdXRob3I6IApkYXRlOiAibGFzdCByZW5kZXJlZDogYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiwgJVknKWAiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRoZW1lOiBwYXBlcgogICAgCiAgICAKLS0tCgoKKioqCgo8YnI+Cgo8Zm9udCBzaXplPSI0LjUiPjxiPlRvb2xzIHRvIGhhbmRsZSB0cmFpdCBtYWNyb2Vjb2xvZ2ljYWwgZGF0YXNldHMgaW4gUjwvYj48L2ZvbnQ+CgoKVGhlIHJtYWNyb1JETSBwYWNrYWdlIGNvbnRhaW5zIGZ1bmN0aW9ucyB0byBoZWxwIHdpdGggdGhlIGNvbXBpbGF0aW9uIG9mCm1hY3JvZWNvbG9naWNhbCBkYXRhc2V0cy4gSXQgY29tcGlsZXMgZGF0YXNldHMgaW50byBhICoqbWFzdGVyIGxvbmcKZGF0YWJhc2Ugb2YgaW5kaXZpZHVhbCBvYnNlcnZhdGlvbnMqKiwgbWF0Y2hlZCB0byBhIHNwZWNpZmllZCAqKm1hc3RlcgpzcGVjaWVzIGxpc3QqKi4gSXQgYWxzbyAqY2hlY2tzLCBzZXBhcmF0ZXMgYW5kIHN0b3JlcyB0YXhvbm9taWMgYW5kCm1ldGFkYXRhIGluZm9ybWF0aW9uKiBvbiB0aGUgKm9ic2VydmF0aW9ucyosICp2YXJpYWJsZXMqIGFuZCAqZGF0YXNldHMqCmNvbnRhaW5lZCBpbiB0aGUgZGF0YS4gSXQgdGhlcmVmb3JlIGFpbXMgdG8gZW5zdXJlIGZ1bGwgdHJhY2VhYmlsaXR5IG9mCmRhdGFwb2ludHMgYW5kIGFzIHJvYnVzdCBxdWFsaXR5IGNvbnRyb2wsIGFsbCB0aGUgd2F5IHRocm91Z2ggdG8gdGhlCmV4dHJhY3RlZCBhbmFseXRpY2FsIGRhdGFzZXRzLgoKKipGb3IgbW9yZSBkZXRhaWxzIGFuZCBjb250ZXh0LCBzZWUgdGhlIHJtYWNyb1JETSBnaXRodWIgW3JlcG9dKGh0dHBzOi8vZ2l0aHViLmNvbS9hbm5ha3J5c3RhbGxpL3JtYWNyb1JETSkqKgoKUiBjb2RlIGZvciB0aGlzIHdvcmtmbG93IGF2YWlsYWJsZSBbaGVyZV0oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2FubmFrcnlzdGFsbGkvcm1hY3JvUkRNL21hc3Rlci91dGlscy90ZW1wX3ZpZ25ldHRlLlIpCgoqKioKPGJyPgoKIyBzZXR1cAoKYGBge3IgZ2xvYmFsLXNldHVwLCBlY2hvID0gRn0Kcm0obGlzdD1scygpKQpvcHRpb25zKHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpgYGAKCmBgYHtyIHJtZC1zZXR1cCwgZWNobz1GQUxTRSwgcHVybD1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KcmVxdWlyZShSQ3VybCkKcmVxdWlyZShrbml0cikKbGlicmFyeShsaXN0dmlld2VyKQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKCiMjIHNvdXJjZSBybWFjcm9SRE0gZnVuY3Rpb25zCgpGaXJzdCBzb3VyY2UgdGhlIHJtYWNyb1JETSBmdW5jdGlvbnMuIEN1cnJlbnRseSB0aGUgYmVzdCB3YXkgaXMgdG8ganVzdCBzb3VyY2UgZnJvbSBnaXRodWIgdXNpbmcgYFJDdWxyOmdldFVSTCgpYC4KCioqKldBUk5JTkc6IFJlcG8gdW5kZXIgY29udGludW91cyBkZXZlbG9wbWVudCoqKgoKYGBge3Igc291cmNlLXJtYWNyb1JETSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcmVxdWlyZShSQ3VybCkKZXZhbChwYXJzZSh0ZXh0ID0gZ2V0VVJMKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vYW5uYWtyeXN0YWxsaS9ybWFjcm9SRE0vbWFzdGVyL1IvZnVuY3Rpb25zLlIiLCBzc2wudmVyaWZ5cGVlciA9IEZBTFNFKSkpCmV2YWwocGFyc2UodGV4dCA9IGdldFVSTCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2FubmFrcnlzdGFsbGkvcm1hY3JvUkRNL21hc3Rlci9SL3dpZGVEYXRhX2Z1bmN0aW9uLlIiLCBzc2wudmVyaWZ5cGVlciA9IEZBTFNFKSkpCgpgYGAKCiMjIHNldHVwIGZpbGUuc3lzdGVtCgpOZXh0LCB0byBpbml0aWFsaXNlIHRoZSBwcm9qZWN0IHdlIG5lZWQgdG8gc3VwcGx5IHZhbGlkIHBhdGh3YXlzIHRvIHByb2plY3QgZm9sZGVycyBjb250YWluaW5nOgoKLSAqKmRhdGFcOioqIGZvbGRlciBjb250YWluZyBwcml2YXRlIGlucHV0IGFuZCBvdXRwdXQgZm9sZGVycyBhcmUgc3RvcmVkLCB1c3VhbGx5IGEgZ29vZ2xlZHJpdmUgZm9sZGVyKSBhbmQgCi0gKipjb2RlOioqIGZvbGRlciBjb250YWluaW5nIHNjcmlwdHMgYXNzb2NpYXRlZCB3aXRoIHRoZSBwcm9qZWN0LiBUaGlzIGlzIHVzdWFsbHkgYW4gUlN0dWRpbyBwcm9qZWN0IGRpcmVjdG9yeSwgaWRlYWxseSB2ZXJzaW9uIGNvbnRyb2xsZWQgb24gZ2l0aHViLgoKVGhlIGZ1bmN0aW9uIHNldHMgdGhvc2UgZGlyZWN0b3JpZXMgaW4gdGhlIGdsb2JhbCBlbnZpcm9ubWVudC4KCmBgYHtyIHNldC1kaXJzfQoKc2V0RGlyZWN0b3JpZXMoc2NyaXB0LmZvbGRlciA9ICJ+L0RvY3VtZW50cy93b3JrZmxvd3MvQnJhaW5fc2l6ZV9ldm9sdXRpb24vIiwgCiAgICAgICAgICAgICAgIGRhdGEuZm9sZGVyID0gIn4vR29vZ2xlIERyaXZlL0JyYWluIGV2b2x1dGlvbi8iLAogICAgICAgICAgICAgICBlbnZpciA9IGdsb2JhbGVudigpKQoKYGBgCgpPbmNlIHByb2plY3QgZm9sZGVycyBoYXZlIGJlZW4gc2V0LCB3ZSBzZXQgdXAgdGhlICpmaWxlIHN5c3RlbSogYnkgY3JlYXRpbmcgdGhlIHJlcXVpcmVkIGZvbGRlcnMgKGlmIHRoZXkgZG9uJ3QgZXhpc3QgYWxyZWFkeSkgaW4gdGhlIHByb2plY3QgZm9sZGVycy4gCgpgYGB7ciBzZXR1cC1maWxlLnN5c3RlbX0KCnNldHVwRmlsZVN5c3RlbShzY3JpcHQuZm9sZGVyID0gIn4vRG9jdW1lbnRzL3dvcmtmbG93cy9CcmFpbl9zaXplX2V2b2x1dGlvbi8iLCAKICAgICAgICAgICAgICAgIGRhdGEuZm9sZGVyID0gIn4vR29vZ2xlIERyaXZlL0JyYWluIGV2b2x1dGlvbi8iKQpgYGAKCiMjIGluaXRpYWxpc2UgZGF0YWJhc2UgY29uZmlndXJhdGlvbnMgYW5kIGF0dGFjaCB0byBzZWFyY2ggcGF0aHdheQoKSW4gdGhpcyBzdGVwLCB3ZSBpbml0aWFsaXNlIHRoZSBlbnZpcm9ubWVudCB3aXRoIHNvbWUgcmVxdWlyZWQgcGFyYW1ldGVycyB0byBidWlsZCB0aGUgZGF0YWJhc2UgYW5kIHByb2Nlc3MgZmlsZXMgdG8gaXQuIFRoZSBjYWxsIGJlbG93IHNob3dzIHRoZSBkZWZhdWx0IGluaXRpYWxpc2F0aW9uIHNldHRpbmdzIHdoaWNoIHlvdSB3b3VsZCBnZXQgaWYgeW91IGp1c3QgY2FsbGVkIGBpbnRpX2RiKClgCgpgYGB7ciBtYXN0ZXItY29uZmlndXJhdGlvbi1kZWZhdWx0LCBldmFsPUYsIHB1cmw9RkFMU0V9CmluaXRfZGIodmFyLnZhcnMgPSBjKCJ2YXIiLCAidmFsdWUiLCAiZGF0YS5JRCIpLAogICAgICAgICAgICAgICAgICAgIG1hdGNoLnZhcnMgPSBjKCJzeW5vbnltcyIsICJkYXRhLnN0YXR1cyIpLAogICAgICAgICAgICAgICAgICAgIG1ldGEudmFycyA9IGMoInFjIiwgIm9ic2VydmVyIiwgInJlZiIsICJuIiwgIm5vdGVzIiksCiAgICAgICAgICAgICAgICAgICAgdGF4by52YXJzID0gYygiZ2VudXMiLCAiZmFtaWx5IiwgIm9yZGVyIiksCiAgICAgICAgICAgICAgICAgICAgc3BwLmxpc3Rfc3JjID0gTlVMTCkKYGBgCgpJIGFjdHVhbGx5IHdhbnQgdG8gc2V0ICoqIkQwIioqIGFzIHRoZSBmaWxlIGZyb20gd2hpY2ggdG8gZXh0cmFjdCB0aGUgc3BwLmxpc3QgaW4gYSBiaXQgc28gSSBzZXQgYHNwcC5saXN0X3NyYyA9ICJEMCJgLgoKYGBge3IgbWFzdGVyLWNvbmZpZ3VyYXRpb24sIGV2YWw9VH0KaW5pdF9kYihzcHAubGlzdF9zcmMgPSAiRDAiKQpgYGAKClRoZSBmdW5jdGlvbiBhcHBlbmRzIHRoZSBnaXZlbiBhcmd1bWVudHMgdG8gZW52aXJvbm1lbnQgYG1hc3Rlcl9jb25maWdgIGF0IHBvc2l0aW9uIDIgaW4gdGhlIHNlYXJjaCBwYXRoIChub3RlIHBvc2l0aW9uIG9mIGBHbG9iYWxFbnZpcm9ubWVudGAgPSAxKS4gCgpIZXJlJ3MgYSBsaXN0IG9mIHRoZSB2YWx1ZXMgb2YgdGhlIG9iamVjdHMgd2UganVzdCBhdHRhY2hlZCBhcyBjb25maWd1cmF0aW9uczoKYGBge3IgcHJpbnQtbWFzdGVyX2NvbmZpZywgZWNobz1GQUxTRSwgcHVybD1GQUxTRX0KbXNfY29uZiA8LSBzZXROYW1lcyhsYXBwbHkobHMoIm1hc3Rlcl9jb25maWciKSwgRlVOID0gZ2V0LCBlbnZpciA9IGVudmlyb25tZW50KCkpLCAKICAgICAgICAgbHMoIm1hc3Rlcl9jb25maWciKSkKanNvbmVkaXQobXNfY29uZikKYGBgCgoKIyMgc2V0dXAgaW5wdXQuZm9sZGVyCgpOZXh0IHdlIHNldHVwIHRoZSBmb2xkZXJzIGluIGBpbnB1dC5mb2xkZXIvcHJlL2AgYW5kIGBwb3N0L2AgYWNjb3JkaW5nIHRvIHRoZSBjb25maWd1cmF0aW9ucyBzZXQgaW4gdGhlIHByZXZpb3VzIHN0ZXAuCgpJZiB0aGUgY29ycmVjdCBzZXR1cCBhbHJlYWR5IGV4aXN0cywgbm8gYWN0aW9uIGlzIHRha2VuOgoKYGBge3Igc2V0dXAtaW5wdXQuZm9sZGVyfQpzZXR1cElucHV0Rm9sZGVyKGlucHV0LmZvbGRlcikKYGBgCgoKYGBge3IgcHJpbnQtaWEtZm9sZGVyLXN0ciwgcHVybD1GQUxTRX0KZGlycyA8LSBsaXN0LmRpcnMocGFzdGUoaW5wdXQuZm9sZGVyLCAicHJlLyIsIHNlcCA9ICIiKSwgZnVsbC5uYW1lcyA9IFQpCgpwcmludChkaXJzKQpgYGAKCjxicj4KCioqKgoKIyBwb3B1bGF0ZSBmaWxlLnN5c3RlbQoKVGhlIGZ1bmN0aW9ucyB0YWtlIGFkdmFudGFnZSBvZiB0aGUgc3RydWN0dXJlIG9mIHRoZSBmaWxlLnN5dGVtIHRvIGF1dG9tYXRlIGxvYWRpbmcgYW5kIGxpbmtpbmcgb2YgZGF0YSBhbmQgbWV0YWRhdGEgdGhyb3VnaCBhcHByb3ByaWF0ZSBuYW1pbmcgYW5kIGxvY2F0aW9uIG9mIGZpbGVzIHdpdGhpbiB0aGUgZmlsZS5zeXN0ZW0uCgotICoqYHJhdy9gKiogb3JnYW5pc2UgYWxsIHJhdyBkYXRhCi0gKipgcHJlL2Agc2F2ZSBjb3BpZXMgb2YgdGhlIHJhdyBkYXRhIGZpbGVzIGluIHRoZSBhcHByb3ByaWF0ZSBkYXRhIChgY3ZzYCkgb3IgYG1ldGEudmFyc2AgZm9sZGVycy4qKgoKKioqTkIqKiogKmBtZXRhLnZhcmAgZGF0YSBzaGVldHMgc2hvdWxkIGJlIG5hbWVkIHdpdGggdGhlIHNhbWUgbmFtZSBhcyB0aGUgYGRhdGFgIGRhdGEgc2hlZXQuIFRha2UgY2FyZSBkdXJpbmcgdGhpcyBzdGFnZSB0byBlbnN1cmUgZmlsZXMgYXJlIG5hbWVkIGNvcnJlY3RseSBhbmQgc3RvcmVkIGluIHRoZSBhcHByb3ByaWF0ZSBmb2xkZXJzLiogCgojIGxvYWQgcmVxdWlyZWQgZGF0YQoKIyMgbWFrZSBmY29kZXMgKGZvbGRlciBjb2RlcykgdmVjdG9yCgpUaGUgYGZjb2Rlc2AgdmVjdG9yIHNwZWNpZmllcyB0aGUgZGV0YWlscyBvZiBmb2xkZXJzIGluIGBwcmUvYCBhbmQgYHBvc3QvYCBgaW5wdXQuZm9sZGVyYCBmb2xkZXJzLiBJdCBhbHNvIGNyZWF0ZXMgYXBwcm9wcmlhdGUgY29kZSBwcmVmaXhlcyBmb3IgZWFjaCB0eXBlIG9mIGBkYXRhYCBvciBgbWV0YS52YXJgIHNoZWV0LiBOb3RlIHRoYXQgKioiRCIqKiBpcyByZXNlcnZlZCBmb3IgKipgZGF0YWAqKiBmaWxlcywgKioiUiIqKiBmb3IgKipgcmVmYCoqIGZpbGVzIGFuZCAqKiJOIioqIGZvciAqKmBuYCoqIGZpbGVzLgoKYGBge3IgZW5zdXJlLWZjb2Rlc30KZmNvZGVzIDwtIGVuc3VyZV9mY29kZXMobWV0YS52YXJzKQoKYGBgCgpgYGB7ciBwcmludC1mY29kZXMsIHB1cmw9RkFMU0V9CnByaW50KGZjb2RlcykKYGBgCgoKIyMgc2V0IGZpbGUubmFtZXMgdmVjdG9yCgpTcGVjaWZ5IHRoZSBgZmlsZS5uYW1lc2Agb2YgdGhlIGZpbGVzIHlvdSB3aXNoIHRvIHByb2Nlc3MuIElmIHlvdSB3YW50IHRvIHByb2Nlc3MgYWxsIGZpbGVzIGluIHRoZSBmaWxlLnN5c3RlbSB1c2UgYGZpbGUubmFtZXMgPSBOVUxMYC4gCgpgYGB7ciBzZXQtZmlsZS5uYW1lc30KZmlsZS5uYW1lcyA8LSBjcmVhdGVfZmlsZS5uYW1lcyhmaWxlLm5hbWVzID0gYygiYnJhaW5tYWluMi5jc3YiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJBbW5pb3RlX0RhdGFiYXNlX0F1Z18yMDE1LmNzdiIsICJhbmFnZWRhdGFzZXRmLmNzdiIpKQoKCmBgYAoKYGBge3IgcHJpbnQtZmlsZS5uYW1lcywgcHVybD1GQUxTRX0KcHJpbnQoZmlsZS5uYW1lcykKYGBgCgoKIyMgbG9hZCBzeXN0ZW0gcmVmZXJlbmNlIGZpbGVzCgpMb2FkIHRoZSBzeXN0ZW0gcmVmZXJlbmNlIChzeXMucmVmKSBmaWxlcyByZXF1aXJlZCBmb3IgZGF0YSBwcm9jZXNzaW5nLgpgYGB7ciBsb2FkLXN5cy5yZWZ9CmxvYWRfc3lzLnJlZihmaWxlRW5jb2RpbmcgPSAibWFjIiwgdmlldyA9IEYpCmBgYAoqdXNlIGB2aWV3ID0gVGAgdG8gb3BlbiBhIHZpZXdlciBmb3IgZWFjaCBvZiB0aGUgc3lzLnJlZiBmaWxlcyBvbiBsb2FkLgoKIyMjIGBtZXRhZGF0YS5jc3ZgCgpgYGB7ciB0YWJsZS1tZXRhZGF0YSwgcHVybD1GQUxTRX0KCmthYmxlKGhlYWQobWV0YWRhdGEsIDEwKSkKYGBgCgoKIyMjIGBkYXRhX2xvZy5jc3ZgCgpgYGB7ciB0YWJsZS1kYXRhX2xvZywgcHVybD1GQUxTRX0KCgprYWJsZShkYXRhX2xvZykKYGBgCgojIyMgYHZuYW1lcy5jc3ZgCgpgYGB7ciB0YWJsZS12bmFtZXMsIHB1cmw9RkFMU0V9CgprYWJsZShoZWFkKHZuYW1lcywgMTApKQpgYGAKCiMjIGxvYWQgYHN5bi5saW5rcy5jc3ZgCgpVc2VkIGZvciB0YXhvbm9taWMgbWF0Y2hpbmcgKHBsYW5zIHRvIGF1dG9tYXRlIHRoaXMgYnkgaW50ZWdyYXRpbmcgcGFja2FnZSBgdGF4aXplYC4gc3VwcGxpZWQgc3luLmxpbmtzIG9ubHkgcmVsYXRlcyB0byBiaXJkcykuCgpgYGB7ciBsb2FkLXN5bi5saW5rc30Kc3luLmxpbmtzIDwtIHJlYWQuY3N2KHRleHQ9Z2V0VVJMKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vYW5uYWtyeXN0YWxsaS9ybWFjcm9SRE0vbWFzdGVyL2RhdGEvaW5wdXQvdGF4by9zeW4ubGlua3MuY3N2IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzc2wudmVyaWZ5cGVlciA9IEZBTFNFKSwgaGVhZGVyPVQpCmBgYAoKPGJyPgoKKioqCgojIHByb2Nlc3MgZmlsZS5zeXN0ZW0KClRoaXMgc3RlcCBwcm9jZXNzZXMgdGhlIGAuY3N2YCBjb3BpZXMgb2YgdGhlIHJhdyBkYXRhIGluIHRoZSBgcHJlL2AgZm9sZGVyIGFuZCB3cml0ZXMgdGhlIHByb2Nlc3NlZCBmaWxlcyBhcyBgLmNzdmAgdG8gdGhlIGBwb3N0L2AgZm9sZGVyLCByZWFkeSB0byBiZSBtYXRjaGVkLiBUaGUgcHJvY2Vzc2luZyBjb25zZXJ2ZXMgdGhlIGZpbGUubmFtZXMgdGhyb3VnaG91dC4gSXQgaXMgaW1wb3J0YW50IGZvciB0aGlzIHN0ZXAgdGhhdCB0aGUgZmlsZS5zeXN0ZW0gaXMgY29ycmVjdGx5IHBvcHVsYXRlZCAoaWUuIGBtZXRhLnZhcmAgZGF0YSBzaGVldHMgc2hvdWxkIGJlIG5hbWVkIHdpdGggdGhlIHNhbWUgbmFtZSBhcyB0aGUgYGRhdGFgIGRhdGEgc2hlZXQgYW5kIGluIHRoZSBjb3JyZWN0IGZvbGRlcikuIAoKCgo8YnI+CgoqKlRoZSBmdW5jdGlvbiBydW5zIGEgYmFzaWMgcHJvY2Vzc2luZyBzdGFnZSBmb3IgZWFjaCBmaWxlIGluIGBmaWxlLm5hbWVzYDoqKgoKLSBwcm9jZXNzaW5nIGlzIGl0ZXJhdGVkIG92ZXI6CiAgICAtICoqYWxsIGZpbGUubmFtZXMqKiBhdmFpbGFibGUgaW4gdGhlIGZpbGUuc3lzdGVtIGlmIGBmaWxlLnN5c3RlbSA9ICJmcm9tRlMiYCwKICAgIC0gKipmaWxlcyBzcGVjaWZpZWQgaW4gYGZpbGUubmFtZXNgKiogaWYgYGZpbGUubmFtZXNgIGlzIHZlY3RvciBvZiBmaWxlIG5hbWVzIChub3RlIHRoYXQgb25seSBmaWxlcyBhdmFpbGFibGUgaW4gdGhlIGZpbGUuc3lzdGVtIGFyZSBwcm9jZXNzZWQpLgotIGRhdGEgYXJlIGxvYWRlZCwgdHJpbW1lZCBvZiB3aGl0ZXNwYWNlLCBibGFuayBsaW5lcyBhbmQgYGMoIiIsICIgIiwgIk5BIiwgIi05OTkiKWAgY29kZWQgYXMgYE5BYHMgYnkgZGVmYXVsdC4KLSBjb2x1bW4gbmFtZXMgaXMgdGhlIGRhdGEgYXJlIG1hdGNoZWQgdG8gbWFzdGVyIHZhcmlhYmxlIGNvZGVzIHRocm91Z2ggYHZuYW1lc2AKLSBpZiB0aGUgb3JpZ2luYWwgZGF0YXNldCBjb250YWlucyBzcGVjaWVzIGRldGFpbHMgYWNyb3NzIHR3byBjb2x1bW5zIChpZSAqc3BlY2llcyogYW5kICpnZW51cyopLCBkYXRhIGluIHRoZSBjb2x1bW5zIGFyZSBjb25jYXRpbmF0ZWQgaW4gdGhlIGZvcm0gYCJnZW51c19zcGVjaWVzImAgYW5kIG1lcmdlZCBpbnRvIGEgc2luZ2xlIGBzcGVjaWVzYCBjb2x1bW4uICoqKmVuc3VyZSBjb2x1bW4gaW4gZmlsZXMgY29udGFpbmluZyBnZW51cyBkYXRhIGlzIG1hdGNoZWQgdG8gY29kZSBgZ2VudXNgIGluIGB2bmFtZXNgLioqKiAKCjxicj4KCioqY3VzdG9tIHByb2Nlc3Npbmcgc2NyaXB0cyoqCgp5b3UgY2FuIGluY2x1ZGUgZXh0cmEgcHJvY2Vzc2luZyBzY3JpcHRzIGZvciBpbmRpdmlkdWFsIGZpbGVzIGJ5IGFkZGluZyB0aGVtIGludG8gdGhlIGB7c2NyaXB0LmZvbGRlcn1wcm9jZXNzL2AgZm9sZGVyLiBUbyBiZSBsb2FkZWQgY29ycmVjdGx5LCAqKnNjcmlwdHMgbmVlZCB0byBiZSBuYW1lZCBhcHByb3ByaWF0ZWx5OioqCgotIHRvIHNvdXJjZSBhY3Jvc3MgKmFsbCBmaWxlcyogaW4gZmlsZS5zeXN0ZW0gbWF0Y2hpbmcgdGhlIGBmaWxlLm5hbWVgOiBuYW1lIHNjcmlwdCBhcyBgZmlsZS5uYW1lYC4gZWcgLmAiQW1uaW90ZV9EYXRhYmFzZV9BdWdfMjAxNS5SImAKLSB0byBzb3VyY2UgZm9yICpmaWxlcyBpbiBhIHNwZWNpZmljIGZvbGRlciogaW4gZmlsZS5zeXN0ZW0gbWF0Y2hpbmcgdGhlIGBmaWxlLm5hbWVgOiBuYW1lIHNjcmlwdCBhcyBgZmlsZS5uYW1lYCBhcHBlbmRlZCB3aXRoIGFwcHJvcHJpYXRlIGBmY29kZWAsIGVnIC5gIkFtbmlvdGVfRGF0YWJhc2VfQXVnXzIwMTVfcmVmLlIiYAoKCmBgYHtyIHByb2Nlc3MtY3N2cywgd2FybmluZz1GQUxTRX0KCnByb2Nlc3NfZmlsZS5zeXN0ZW0oZmlsZS5uYW1lcywgZmNvZGVzKQoKYGBgCgo8YnI+CgoqKioKCiMgQ3JlYXRlIGRhdGFiYXNlCgojIyBjcmVhdGUgc3BwLmxpc3QKClVzZSBgc3BwLmxpc3Rfc291cmNlYCB0byBzcGVjaWZ5IGRjb2RlIG9mIGZpbGUubmFtZSB0byBleHRyYWN0IHNwcC5saXN0IGZyb20uIE90aGVyd2lzZSwgCnN1cHBseSB2ZWN0b3Igb2Ygc3BlY2llcyBuYW1lcyB0byBgc3BlY2llc2AuCgpgYGB7ciBjcmVhdGUtc3BwLmxpc3R9CnNwcC5saXN0IDwtIGNyZWF0ZVNwcC5saXN0KHNwZWNpZXMgPSBOVUxMLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgdGF4by5kYXQgPSBOVUxMLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BwLmxpc3Rfc3JjID0gc3BwLmxpc3Rfc3JjKQoKYGBgCgpgYGB7ciBwcmludC1zdHItc3BwLmxpc3QsIHB1cmw9RkFMU0V9CnN0cihzcHAubGlzdCwgdmVjLmxlbiA9IDMpCmBgYAoKCiMjIGNyZWF0ZSBtYXN0ZXIgc2hlbGwKCmBgYHtyIGNyZWF0ZS1tYXN0ZXJ9Cm1hc3RlciA8LSBjcmVhdGVfbWFzdGVyKHNwcC5saXN0KQpgYGAKCmBgYHtyIHByaW50LXN0ci1tYXN0ZXIsIHB1cmw9RkFMU0V9CnN0cihtYXN0ZXIsIG1heC5sZXZlbCA9IDIsIHZlYy5sZW4gPSAzKQpgYGAKCjxicj4KCioqKgoKIyBtYXRjaCBuZXcgZGF0YXNldHMgdG8gbWFzdGVyCgojIyBjcmVhdGUgbWF0Y2ggb2JqZWN0IGZyb20gZmlsZS5uYW1lLiAKClRoZSBmdWN0aW9uIGxvYWRzIHRoZSBmaWxlIHNwZWNpZmllZCBieSBgZmlsZW5hbWVgIGluIGBpbnB1dC5mb2xkZXIvcHJlL2Nzdi9gLiBUaGUgYXJndW1lbnQgYHN1YmAgc3BlY2lmaWVzIHdoaWNoIG9mIHRoZSB0d28gc2V0cyBvZiBzcGVjaWVzIHRvIGJlIG1hdGNoZWQgKGBzcHAubGlzdGAgb3IgYGRhdGFgKSBpcyBhIHN1YnNldCAoaWUgc21hbGxlcikgdGhhbiB0aGUgb3RoZXIuIGBzcHAubGlzdGAgaXMgdGhlIHNwcC5saXN0IGF0dGFjaGVkIHRvIHRoZSBtYXN0ZXIuCmBgYHtyIGNyZWF0ZS1tfQoKZmlsZW5hbWUgPC0gZmlsZS5uYW1lc1tmaWxlLm5hbWVzID09ICJBbW5pb3RlX0RhdGFiYXNlX0F1Z18yMDE1LmNzdiJdCgptIDwtIG1hdGNoT2JqKGZpbGUubmFtZSA9IGZpbGVuYW1lLAogICAgICAgICAgICAgIHNwcC5saXN0ID0gbWFzdGVyJHNwcC5saXN0LAogICAgICAgICAgICAgIHN1YiA9ICJzcHAubGlzdCIpICMgdXNlIGFkZE1ldGEgZnVuY3Rpb24gdG8gbWFudWFsbHkgYWRkIG1ldGFkYXRhLgoKYGBgCgpgYGB7ciBwcmludC1wcmVtYXRjaC1tLCBlY2hvPUZBTFNFLCBwdXJsPUZBTFNFfQpzdHIobSwgbWF4LmxldmVsID0gMSwgdmVjLmxlbiA9IDMpCmBgYAoKIyMgY29tcGlsZSBtZXRhZGF0YSBhbmQgcHJlcGFyZSBtIGZvciBtYXRjaGluZwoKYGBge3IgcHJvY2Vzcy1tfQptIDwtIG0gJT4lIAogIHNlcGFyYXRlRGF0TWV0YSgpICU+JSAKICBjb21waWxlTWV0YShpbnB1dC5mb2xkZXIgPSBpbnB1dC5mb2xkZXIpICU+JQogIGNoZWNrVmFyTWV0YShtYXN0ZXIkbWV0YWRhdGEpICU+JQogIGRhdGFNYXRjaFByZXAoKQpgYGAKCgpgYGB7ciBwcmludC1wcmVtYXRjaDEtbSwgZWNobz1GQUxTRSwgcHVybD1GfQpzdHIobSwgbWF4LmxldmVsID0gMSwgdmVjLmxlbiA9IDMpCmBgYAoKIyMgbWF0Y2ggbSB0byBzcHAubGlzdAoKYGBge3IgZGF0YS1zcHAtbWF0Y2h9Cm0gPC0gZGF0YVNwcE1hdGNoKG0sIHN5bi5saW5rcyA9IHN5bi5saW5rcywgYWRkU3BwID0gVCkKYGBgCgoKYGBge3IgcHJpbnQtcG9zdG1hdGNoLW0sIGVjaG89RkFMU0UsIHB1cmw9Rn0Kc3RyKG0sIG1heC5sZXZlbCA9IDEsIHZlYy5sZW4gPSAzKQpgYGAKCiMjIyAKCmBgYHtyIG91dHB1dH0Kb3V0cHV0IDwtIG1hc3RlckRhdGFGb3JtYXQobSwgbWV0YS52YXJzLCBtYXRjaC52YXJzLCB2YXIudmFycykKYGBgCgpgYGB7ciBwcmludC1vdXRwdXQsIHB1cmw9Rn0Kc3RyKG91dHB1dCwgbWF4LmxldmVsID0gMSwgdmVjLmxlbiA9IDMpCmBgYAoKCiMjIyBtZXJnZSB0byBtYXN0ZXIKCmBgYHtyIG1lcmdlLXRvLW1hc3Rlcn0KbWFzdGVyIDwtIHVwZGF0ZU1hc3RlcihtYXN0ZXIsIG91dHB1dCA9IG91dHB1dCkKYGBgCgpgYGB7ciBwcmludC1tYXN0ZXIsIHB1cmw9Rn0Kc3RyKG1hc3RlciwgbWF4LmxldmVsID0gMSwgdmVjLmxlbiA9IDMpCmBgYAoKCioqKgo8YnI+CgojIGludGVyYWN0aXZlIHZpZXcgb2YgZnJhbWV3b3JrIG9iamVjdHM6CgojIyBtYXN0ZXIKYGBge3IgcHJpbnQtaWEtbWFzdGVyLCBwdXJsPUZ9Cmpzb25lZGl0KG1hc3RlcikKYGBgCgo8YnI+CgojIyBtYXRjaGVkIG0KYGBge3IgcHJpbnQtaWEtbSwgcHVybD1GfQpqc29uZWRpdChtKQpgYGAKCg==